/* 
 * File:   xxShell.cpp
 * Author: kuba
 *
 * Created on 29. říjen 2012, 19:47
 */

#include <cstdlib>
#include <cstdio>
#include <readline/history.h>
#include <readline/readline.h>
#include <unistd.h>
#include <limits.h>
#include <list>
#include <sys/types.h>
#include <dirent.h>

#include "Interpret.h"
#include "JobControl.h"
#include "Inter.h"
#include "SingleCommand.h"
#include "ParseException.h"


#define BUFFER_SIZE 1<<16
#define ARR_SIZE 1<<16
#define MAX_PROMPT_LEN 1<<10

using std::list;
using std::string;

extern list<SingleCommand *>* analyze(char*);

// funkcni prototypy
static void init_readline();
static char* read_command(char* buffer);
static char* replace_str(char* str, const char* orig, const char* rep);

char user_dir_buffer[BUFFER_SIZE];


int main(int argc, char* argv[])
{
  char buffer[BUFFER_SIZE];
  char* args[ARR_SIZE];
  Inter::cd(args, 1);
  Interpret interpret;

  set_sighdls(true);
  init_readline();

  getcwd(user_dir_buffer, BUFFER_SIZE);

  list<SingleCommand*> * cmdLine;

  // zakladni smycka shellu
  while (true)
  {
    if (read_command(buffer) == NULL)
    {
      Inter::exit_shell();
      continue;
    }

    try
    {
      cmdLine = analyze(buffer);
      if (cmdLine == NULL || cmdLine->size() == 0)
        continue;

      interpret.interpretCmd(*cmdLine);

      for (list<SingleCommand*>::iterator it = cmdLine->begin(); it != cmdLine->end(); it++)
      {
        if ((*it) != NULL)
        {
          delete (*it);
          (*it) = NULL;
        }
      }
      delete(cmdLine);
    } catch (ParseException& caught)
    {
      printf("Error: %s\n", caught.what());
    }
  }

  return 0;
}


/**
 * Funkce pro nahrazeni casti retezce jinym retezcem. Podretezec orig v retezci str 
 * bude nahrazen podretezcem rep pokud pokud str orig obsahuje, jinak nedojde k zadne 
 * zmene.
 * 
 * @param str   Vstupni retezec.
 * @param orig  Nahrazovany retezec.
 * @param rep   Nahrazujici retezec.
 * @return      Retezec s nahrazenym podretezcem.
 */
static char* replace_str(char* str, const char* orig, const char* rep)
{
  static char buffer[4096];
  char *p;

  if (!(p = strstr(str, orig))) // Obsahuje hledany retezec?
    return str;

  strncpy(buffer, str, p - str); // Kopiruje zacatek 'str' do 'orig'
  buffer[p - str] = '\0';

  sprintf(buffer + (p - str), "%s%s", rep, p + strlen(orig));

  return buffer;
}






/* ________________
 *   GNU Readline
 */

char** cmd_completion(const char* text, int start, int end);
char* cmd_generator(const char* text, int state);


/**
 * Inicializuje knihovnu readline - nacte historii prikazu ze souboru a nastavi vlastni
 * funkce pro tab-completion.
 */
static void init_readline()
{
  char filename[PATH_MAX];

  snprintf(filename, PATH_MAX, "%s/%s", getenv("HOME"), HISTORY_FILENAME);
  read_history(filename);
  stifle_history(MAX_HISTORY_LEN);

  rl_readline_name = "xxShell";
  rl_attempted_completion_function = cmd_completion;
}


/**
 * Funkce se pokusi aplikovat tab-completion.
 *  
 * @param text Retezec k doplneni.
 * @param start Zacatek retezce v rl_line_buffer.
 * @param end Konec retezce v rl_line_buffer.
 * @return Vraci pole doplnenych retezcu nebo NULL.
 */
char** cmd_completion(const char* text, int start, int end)
{
  char **matches;

  matches = (char **) NULL;

  // pokud je slovo na zacatku radky - doplnit prikaz, jinak doplnujem nazev souboru
  if (start == 0)
    matches = rl_completion_matches(text, cmd_generator);

  return matches;
}


/**
 * Funkce generuje retezce pro tab-completion. Hleda vestavene prikazy shellu a programy
 * v systemove PATH.
 * 
 * @param text Retezec k doplneni.
 * @param state Indikuje pocet vyvolani funkce pro dany retezec.
 * @return Jedna konkretni moznost doplneni retezce. Retezec je alokovan pomoci malloc.
 */
char* cmd_generator(const char* text, int state)
{
  static int bcmd_index, len;
  static std::list<string> prog_list;
  const char *name;
  char *name_copy;

  // pokud je k doplneni novy retezec - inicializace potrebnych hodnot
  if (!state)
  {
    bcmd_index = 0;
    len = strlen(text);
    prog_list.clear();

    char *path, *path_ro;
    path_ro = getenv("PATH");
    if ((path = (char *) malloc(strlen(path_ro) + 1)) == NULL)
    {
      perror("malloc");
      exit(EXIT_FAILURE);
    }
    strcpy(path, path_ro);
    char *token = strtok(path, ":");
    DIR *dirp;
    struct dirent *dp;
    while (token != NULL)
    {
      if ((dirp = opendir(token)) != NULL)
      {
        while ((dp = readdir(dirp)) != NULL)
          if (dp->d_reclen >= len && !strncmp(dp->d_name, text, len))
            prog_list.push_back(string(dp->d_name));
        (void) closedir(dirp);
      }
      token = strtok(NULL, ":");
    }
    free((void *) path);
  }

  // doplneni vestavenych prikazu
  while (name = BUILTIN_CMDS[bcmd_index])
  {
    bcmd_index++;

    if (strncmp(name, text, len) == 0)
    {
      if ((name_copy = (char *) malloc(strlen(name) + 1)) == NULL)
      {
        perror("malloc");
        exit(EXIT_FAILURE);
      }
      strcpy(name_copy, name);
      return name_copy;
    }
  }

  // doplneni spustitelnych programu v PATH
  while (!prog_list.empty())
  {
    string prog = prog_list.front();
    prog_list.erase(prog_list.begin());
    if ((name_copy = (char *) malloc(prog.size() + 1)) == NULL)
    {
      perror("malloc");
      exit(EXIT_FAILURE);
    }
    strcpy(name_copy, prog.c_str());
    return name_copy;
  }

  // nic dalsiho nenalezeno
  return ((char *) NULL);
}


/**
 * Nacte prikaz od uzivatele. Vyuziva knihovnu readline.
 * @param buffer buffer do ktereho ma byt ulozen prikaz od uzivatele
 * @return pointer na buffer
 */
static char* read_command(char* buffer)
{
  char prompt[MAX_PROMPT_LEN];
  char *command;

  getcwd(buffer, BUFFER_SIZE);
  snprintf(prompt, MAX_PROMPT_LEN, "%s:%s$ ", getenv("USER"), replace_str(buffer, user_dir_buffer, "~"));
  command = readline(prompt);
  if (command == NULL)
    return NULL;
  if (command && *command && command[0] != ' ')
    add_history(command);
  strncpy(buffer, command, BUFFER_SIZE);
  buffer[BUFFER_SIZE - 1] = '\0'; // pro jistotu
  free((void *) command);

  return buffer;
}
